/*
 * @file libbling/bling-window.c A transparent window with configurable drawing options.
 *
 * @Copyright (C) 2007 John Stowers, Neil Jagdish Patel.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */
 
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gtk/gtk.h>

#if !GTK_CHECK_VERSION(2,9,0)
#include <X11/Xlib.h>
#include <X11/extensions/shape.h>
#include <gdk/gdkx.h>
#endif

#include "libbling/bling-window.h"
#include "bling-color.h"

#define BLING_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), BLING_TYPE_WINDOW, BlingWindowPrivate))

G_DEFINE_TYPE (BlingWindow, bling_window, GTK_TYPE_WINDOW);

enum
{
	PROP_0,
	PROP_ROUNDED,
	PROP_RADIUS,
	PROP_STEP_COLOR_1,
	PROP_STEP_COLOR_2,
	PROP_HISTEP_COLOR_1,
	PROP_HISTEP_COLOR_2,
	PROP_BORDER_COLOR,
	PROP_HILIGHT_COLOR
};

/* STRUCTS & ENUMS */
struct _BlingWindowPrivate
{
	gboolean have_alpha;
	
	/* window appearance settings */
	gboolean rounded_corners;
	gfloat corner_radius;
	
	BlingColor gradient_step_1;
	BlingColor gradient_step_2;
	BlingColor highlight_step_1;
	BlingColor highlight_step_2;
	BlingColor border;
	BlingColor hilight_color;
};


/* FORWARDS */

static void bling_window_class_init(BlingWindowClass *klass);
static void bling_window_init(BlingWindow *window);
static void bling_window_finalize(GObject *obj);
static void bling_window_set_property(GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec);

static gboolean bling_window_button_press_event(GtkWidget *widget, GdkEventButton *event);
static gboolean bling_window_expose_event(GtkWidget *widget, GdkEventExpose *event);

static GtkWindowClass *parent_class;


/* DRAWING FUNCTIONS */

static void
render_rect (BlingWindowPrivate *priv, cairo_t *cr, double x, double y, 
						  double width, double height, 
						  double offset  )
{
	if (priv->rounded_corners) {
        	/* modified from cairo snippets page */
        	double x0  = x ,  	
        	y0	   = y ,
        	rect_width  = width,
        	rect_height = height,
        	radius = priv->corner_radius + offset; 

        	double x1,y1;

        	x1=x0+rect_width;
        	y1=y0+rect_height;

        	cairo_move_to  (cr, x0, y0 + radius);
        	cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
        	cairo_line_to (cr, x1 - radius, y0);
        	cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
        	cairo_line_to (cr, x1 , y1 );
        	cairo_line_to (cr, x0 , y1);

        	cairo_close_path (cr);

	} else 
		cairo_rectangle(cr, x, y, width, height);
}

static void 
draw (GtkWidget *window, cairo_t *cr)
{
	BlingWindowPrivate *priv;
    	double width, height;

	priv = BLING_WINDOW_GET_PRIVATE (window);
	width = window->allocation.width;
	height = window->allocation.height;

	/* clear window to fully transparent */
	cairo_set_source_rgba (cr, 1.0f, 1.0f, 1.0f, 0.0f);
	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
	cairo_paint (cr);
	
	cairo_move_to(cr, 0, 0);
	cairo_set_line_width(cr, 1.0);
	
	cairo_pattern_t *pat;
	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);


	/* main gradient */
	pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
	cairo_pattern_add_color_stop_rgba (	pat, 0.0, 
										priv->gradient_step_1.red,
										priv->gradient_step_1.green,
										priv->gradient_step_1.blue,
										priv->gradient_step_1.alpha);
	cairo_pattern_add_color_stop_rgba (	pat, 1.0, 
										priv->gradient_step_2.red,
										priv->gradient_step_2.green,
										priv->gradient_step_2.blue,
										priv->gradient_step_2.alpha);
	render_rect (priv, cr, 0, 0, width, height, 0);
	cairo_set_source(cr, pat);
	cairo_fill(cr);
	cairo_pattern_destroy(pat);
	
	/* hilight gradient */
	pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
	cairo_pattern_add_color_stop_rgba (	pat, 0.0, 
										priv->highlight_step_1.red,
										priv->highlight_step_1.green,
										priv->highlight_step_1.blue,
										priv->highlight_step_1.alpha);
	cairo_pattern_add_color_stop_rgba (	pat, 1.0, 
										priv->highlight_step_2.red,
										priv->highlight_step_2.green,
										priv->highlight_step_2.blue,
										priv->highlight_step_2.alpha);
	render_rect (priv, cr, 1, 1, width-2, height/4, 0);
	cairo_set_source(cr, pat);
	cairo_fill(cr);
	cairo_pattern_destroy(pat);

	/* internal hi-lightborder 
	cairo_set_source_rgba (cr, 	priv->hilight_color.red,
								priv->hilight_color.green,
								priv->hilight_color.blue,
								priv->hilight_color.alpha);
	render_rect (priv, cr, 1.5, (height/2)+1.5, width-2, height, 1);
	cairo_stroke(cr);*/
	
	/* border */
	cairo_set_source_rgba (	cr, 
							priv->border.red,
							priv->border.green,
							priv->border.blue,
							priv->border.alpha);
	render_rect (priv, cr, 0.5, 0, width, height, 0);
	cairo_stroke(cr);
}

/*  CALLBACKS */


static gboolean
bling_window_button_press_event(GtkWidget *widget, GdkEventButton *event)
{
	switch (event->button) {
	
		case 1:
			gtk_window_begin_move_drag (GTK_WINDOW(widget),
						    event->button,
						    event->x_root,
						    event->y_root,
						    event->time);
			break;
		case 3:
			g_print("Popup a user-settable menu\n");
			break;
		default:
			break;
	}
	return TRUE;
}

static gboolean
bling_window_expose_event(GtkWidget *widget, GdkEventExpose *event)
{
	cairo_t *cr;
	cr = gdk_cairo_create (widget->window);
	draw (widget, cr);
	cairo_destroy (cr);
	
	return GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);
}

#if !GTK_CHECK_VERSION(2,9,0)
/* this is piece by piece taken from gtk+ 2.9.0 (CVS-head with a patch applied
regarding XShape's input-masks) so people without gtk+ >= 2.9.0 can compile and
run input_shape_test.c */
static void 
bling_window_do_shape_combine_mask (GdkWindow* window, GdkBitmap* mask, gint x, gint y)
{
	Pixmap pixmap;
	int ignore;
	int maj;
	int min;

	if (!XShapeQueryExtension (GDK_WINDOW_XDISPLAY (window), &ignore, &ignore))
		return;

	if (!XShapeQueryVersion (GDK_WINDOW_XDISPLAY (window), &maj, &min))
		return;

	/* for shaped input we need at least XShape 1.1 */
	if (maj != 1 && min < 1)
		return;

	if (mask)
		pixmap = GDK_DRAWABLE_XID (mask);
	else
	{
		x = 0;
		y = 0;
		pixmap = None;
	}

	XShapeCombineMask (GDK_WINDOW_XDISPLAY (window),
					   GDK_DRAWABLE_XID (window),
					   ShapeInput,
					   x,
					   y,
					   pixmap,
					   ShapeSet);
}
#endif

static void 
bling_window_update_input_shape (GtkWidget* window, int width, int height)
{
	BlingWindow *priv;
	static GdkBitmap* shape_bitmap = NULL;
	static cairo_t* cr = NULL;

	priv = BLING_WINDOW_GET_PRIVATE (window);

	shape_bitmap = (GdkBitmap*) gdk_pixmap_new (NULL, width, height, 1);
	if (shape_bitmap)
	{
		cr = gdk_cairo_create (shape_bitmap);
		if (cairo_status (cr) == CAIRO_STATUS_SUCCESS)
		{
			draw (window, cr);
			cairo_destroy (cr);

#if !GTK_CHECK_VERSION(2,9,0)
			bling_window_do_shape_combine_mask (window->window, NULL, 0, 0);
			bling_window_do_shape_combine_mask (window->window, shape_bitmap, 0, 0);
#else
			gtk_widget_input_shape_combine_mask (window, NULL, 0, 0);
			gtk_widget_input_shape_combine_mask (window, shape_bitmap, 0, 0);
#endif
		}
		g_object_unref ((gpointer) shape_bitmap);
	}
}


static gboolean 
bling_window_configure_event (GtkWidget* window, GdkEventConfigure* event)
{
	gint new_width = event->width;
	gint new_height = event->height;

	bling_window_update_input_shape (window, new_width, new_height);
	return FALSE;
}

static void 
bling_window_screen_changed (GtkWidget* widget, GdkScreen* old_screen)
{                       
	BlingWindow *window;
	BlingWindowPrivate *priv;
	GdkScreen* new_screen;
	GdkColormap* colormap;
	
	window = BLING_WINDOW(widget);
	priv = BLING_WINDOW_GET_PRIVATE (window);
	new_screen = gtk_widget_get_screen (widget);
	colormap = gdk_screen_get_rgba_colormap (new_screen);
      
	if (!colormap) {
		colormap = gdk_screen_get_rgb_colormap (new_screen);
		priv->have_alpha = FALSE;
	} else
		priv->have_alpha = TRUE;
	
	gtk_widget_set_colormap (widget, colormap);
}


/*  GOBJECT INIT CODE */
static void
bling_window_class_init(BlingWindowClass *klass)
{
	GObjectClass *gobject_class;
	GtkWidgetClass *widget_class;

	parent_class = g_type_class_peek_parent(klass);

	gobject_class = G_OBJECT_CLASS(klass);
	g_type_class_add_private (gobject_class, sizeof (BlingWindowPrivate));
	gobject_class->finalize = bling_window_finalize;
	gobject_class->set_property = bling_window_set_property;
	
	widget_class = GTK_WIDGET_CLASS(klass);
	widget_class->button_press_event = bling_window_button_press_event;
	widget_class->expose_event = bling_window_expose_event;
	widget_class->screen_changed = bling_window_screen_changed;
	widget_class->configure_event = bling_window_configure_event;

	g_object_class_install_property(gobject_class, PROP_ROUNDED,
		g_param_spec_boolean("rounded_corners", "Rounded Corners",
							 "Draw the box with rounded corners",
							 TRUE,
							 G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));

	g_object_class_install_property(gobject_class, PROP_RADIUS,
		g_param_spec_float("radius", "Corner Radius",
							"The radius of the rounded coreners",
							0.0,100.0,20.0,
							G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));

	g_object_class_install_property(gobject_class, PROP_STEP_COLOR_1,
		g_param_spec_string("step_color_1", "Step Color 1",
							"Main gradient from color",
							"454545C8",
							G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));

	g_object_class_install_property(gobject_class, PROP_STEP_COLOR_2,
		g_param_spec_string("step_color_2", "Step Color 2",
							"Main gradient to color",
							"010101BE",
							G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));

	g_object_class_install_property(gobject_class, PROP_HISTEP_COLOR_1,
		g_param_spec_string("histep_color_1", "Histep Color 1",
							"Histep gradient from color",
							"FFFFFF0B",
							G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));

	g_object_class_install_property(gobject_class, PROP_HISTEP_COLOR_2,
		g_param_spec_string("histep_color_2", "Histep Color 2",
							"Histep gradient to color",
							"FFFFFF0A",
							G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));

	g_object_class_install_property(gobject_class, PROP_BORDER_COLOR,
		g_param_spec_string("border_color", "Border Color",
							"Border color",
							"000000CC",
							G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));

	g_object_class_install_property(gobject_class, PROP_HILIGHT_COLOR,
		g_param_spec_string("hilight_color", "Hilight Color",
							"Hilight color",
							"FFFFFF11",
							G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));
}

static void
bling_window_init(BlingWindow *window)
{
	bling_window_screen_changed (GTK_WIDGET(window), NULL);
	gtk_widget_set_app_paintable (GTK_WIDGET(window), TRUE);

	gtk_widget_add_events(GTK_WIDGET(window), GDK_BUTTON_PRESS_MASK);
}



static void
bling_window_finalize(GObject *obj)
{
	BlingWindow *window;
	
	g_return_if_fail(obj != NULL);
	g_return_if_fail(BLING_IS_WINDOW(obj));

	window = BLING_WINDOW(obj);
	

	if (G_OBJECT_CLASS(parent_class)->finalize)
		G_OBJECT_CLASS(parent_class)->finalize(obj);
}

static void
bling_window_set_property(GObject *gobject, guint prop_id,
					const GValue *value, GParamSpec *pspec)
{
	BlingWindow *window;
	BlingWindowPrivate *priv;
	
	window = BLING_WINDOW(gobject);
	priv = BLING_WINDOW_GET_PRIVATE (window);

	switch (prop_id)
	{
		case PROP_ROUNDED:
			priv->rounded_corners = g_value_get_boolean(value);
            break;
        case PROP_RADIUS:
			priv->corner_radius = g_value_get_float(value);
            break;
        case PROP_STEP_COLOR_1:
            bling_color_parse_string (&priv->gradient_step_1, g_value_get_string(value));
            break;
        case PROP_STEP_COLOR_2:
            bling_color_parse_string (&priv->gradient_step_2, g_value_get_string(value));
            break;
        case PROP_HISTEP_COLOR_1:
            bling_color_parse_string (&priv->highlight_step_1, g_value_get_string(value));
            break;
        case PROP_HISTEP_COLOR_2:
            bling_color_parse_string (&priv->highlight_step_2, g_value_get_string(value));
            break;
        case PROP_BORDER_COLOR:
            bling_color_parse_string (&priv->border, g_value_get_string(value));
            break;
        case PROP_HILIGHT_COLOR:
            bling_color_parse_string (&priv->hilight_color, g_value_get_string(value));
            break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
			break;
	}
}

/**
 * bling_window_new
 *
 * Creates a new BlingWindow widget with sensible default appearance.
 * For more control create the base gobject_type with the appropriate 
 * parameters.
 *
 * Returns: a new #BlingWindow
 */
GtkWidget *
bling_window_new(void)
{
    GtkWindow *window = g_object_new(BLING_TYPE_WINDOW, NULL);
	return GTK_WIDGET(window);
}
